namespace Profit
{
    using System;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Cache;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Xml.XPath;
    using System.Runtime.Remoting.Messaging;
    using Core.Base;

    /// <summary>
    /// Application new version checker
    /// </summary>
    public sealed class UpdateChecker : PropertyChangedBase
    {
        private bool _checking;
        private readonly string _url;

        /// <summary>
        /// Occurs when a new version is available
        /// </summary>
        public event EventHandler<NewVersionAvailableEventArgs> NewVersionAvailable;

        /// <summary>
        /// Private constructor
        /// </summary>
        private UpdateChecker(){}
        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="rssurl">The rss feed url to check</param>
        public UpdateChecker(string rssurl)
        {
            if (string.IsNullOrEmpty(rssurl)) throw new ArgumentNullException("rssurl");
            _url = rssurl;
        }
        /// <summary>
        /// Determines if a search is in progress
        /// </summary>
        public bool IsSearching
        {
            get { return _checking; }
            private set { _checking = value; NotifyPropertyChange("IsSearching"); }
        }
        /// <summary>
        /// Begins an async search of a new version
        /// </summary>
        /// <param name="currentVersion">The current application version</param>
        public void SearchNewVersion(Version currentVersion)
        {
            BeginCheckNewVersion(currentVersion);
        }

        /// <summary>
        /// Checks if a new version is available
        /// </summary>
        private void CheckNewVersion(Version currentVersion)
        {
            Trace.TraceInformation(Resources.Messages.label_checking_new_version);

            string link;
            Version newVersion;
            string description;

            if (HasNewVersion(currentVersion, out link, out newVersion, out description))
                InvokeNewVersionAvailable(new NewVersionAvailableEventArgs(newVersion, link, description));
        }
        /// <summary>
        /// Checks if a new version is available
        /// </summary>
        /// <param name="currentVersion">The current application version</param>
        /// <param name="downloadLink">A string with the new version download link if any</param>
        /// <param name="newVersion">The new version if any</param>
        /// <param name="description"></param>
        /// <returns>True if there is a new version otherwise false</returns>
        public bool HasNewVersion(Version currentVersion, out string downloadLink, out Version newVersion, out string description)
        {
            downloadLink = null;
            newVersion = null;
            description = null;
            bool result = false;

            if (!string.IsNullOrEmpty(_url) && currentVersion != null)
                try
                {
                    var request = (HttpWebRequest)WebRequest.Create(_url);
                    request.KeepAlive = false;
                    request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
                    request.Proxy =  WebRequest.GetSystemWebProxy(); 

                    var response = (HttpWebResponse)request.GetResponse();
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        var stream = response.GetResponseStream();
                        if (stream != null)
                        {
                            var nav = new XPathDocument(stream).CreateNavigator();
                            var node = nav.SelectSingleNode("/rss/channel/item/title");
                            if (node != null && !string.IsNullOrEmpty(node.Value))
                            {
                                try
                                {
                                    var match = Regex.Match(node.Value, @"(?<major>[0-9]*)(?:\.)(?<minor>[0-9]*)(?:\.)(?<build>[0-9]*)(?:\.)(?<revision>[0-9]*)");
                                    if(match.Success)
                                        Version.TryParse(match.Value, out newVersion);

                                    if (newVersion!=null && currentVersion < newVersion )
                                    {
                                        var linkNode = nav.SelectSingleNode("/rss/channel/item/link");
                                        if (linkNode != null && !string.IsNullOrEmpty(linkNode.Value))
                                            downloadLink = linkNode.Value;

                                        var descNode = nav.SelectSingleNode("/rss/channel/item/description");
                                        if (descNode != null && !string.IsNullOrEmpty(descNode.Value))
                                        {
                                            var sb = new StringBuilder();
                                            foreach (Match line in Regex.Matches(descNode.Value, "(?:<li>)(?<content>.*)(?:</li>)"))
                                                sb.AppendLine(" - " + line.Groups["content"].Value);

                                            description = sb.ToString();
                                        }
                                    }
                                }
                                catch (Exception)
                                {
                                    stream.Close();
                                    stream.Dispose();

                                    throw;
                                }
                            }

                            stream.Close();
                            stream.Dispose();
                        }
                    }

                    response.Close();
                    result = newVersion!=null && !string.IsNullOrEmpty(downloadLink);
                }
                catch (Exception exp)
                {
                    App.Trace.TraceEvent( 
                        TraceEventType.Error,0, 
                        "An exception of type \"{0}\" occured while checking for a new version. Details : {1}" , exp.GetType(), exp.Message );
                }


            if(!result)
                Trace.TraceInformation("No update found");

           return result;
        }
        /// <summary>
        /// NewVersionAvailable event invocator
        /// </summary>
        /// <param name="e">An UpdateEventArgs instance</param>
        private void InvokeNewVersionAvailable(NewVersionAvailableEventArgs e)
        {
            if(e == null) return;
            EventHandler<NewVersionAvailableEventArgs> handler = NewVersionAvailable;
            if (handler != null) handler(this, e);
        }

        private IAsyncResult BeginCheckNewVersion(Version version)
        {
            IsSearching = true;
            var del = new ActionDelegate<Version>(CheckNewVersion);
            return del.BeginInvoke(version, new AsyncCallback(EndCheckNewVersion), null);
        }
        private void EndCheckNewVersion(IAsyncResult result)
        {
            var del = (ActionDelegate<Version>) ((AsyncResult) result).AsyncDelegate;
            del.EndInvoke(result);
            result.AsyncWaitHandle.Close();

            IsSearching = false;
        }
    }
}